import { EventEmitter, Injectable } from '@angular/core';
import BpmnModeler from 'bpmn-js/lib/Modeler';
import GridBgModule from 'diagram-js-grid-bg';
import minimapModule from 'diagram-js-minimap';
import { ModdleElement } from 'bpmn-js/lib/model/Types';
import newDiagramXml from '@site/resources/newDiagram.bpmn';
import { ElementChangeEvent } from '@site/typing/bpmn.define';
import BpmnModdle from 'bpmn-moddle';
import { v4 as uuidv4 } from 'uuid';
import { is } from 'bpmn-js/lib/util/ModelUtil';
import Modeling from 'bpmn-js/lib/features/modeling/Modeling';
import { debounce, Observable, Subscription } from 'rxjs';
import { Subject } from 'rxjs/internal/Subject';
import { SaveXMLResult } from 'bpmn-js/lib/BaseViewer';
import { CustomProviders } from '@site/app/shared/providers'
import { getElementType } from '@site/utils/bpmn-element';

@Injectable({
  providedIn: 'root'
})
export class BpmnModelerService {

  private readonly modeler: BpmnModeler;

  private currentElement: ModdleElement;

  private elementChanged: EventEmitter<ElementChangeEvent> = new EventEmitter();

  constructor() {
    this.modeler = new BpmnModeler({
      position: 'absolute',
      additionalModules: [
        minimapModule,
        GridBgModule,
        CustomProviders
      ],
      minimap: {open: true},
      gridLine: {gridLineOpacity: 0.1, gridLineStroke: 1}
    });
  }

  getModeler(): BpmnModeler {
    return this.modeler;
  }

  getModeling(): Modeling {
    return this.modeler.get('modeling');
  }

  initModeler() {
    this.modeler.on<any>('selection.changed', ({oldSelection = [], newSelection = []}) => {
      let newSelectionElement;
      if (newSelection.length === 1) {
        newSelectionElement = newSelection[0];
      } else {
        newSelectionElement = this.modeler.getDefinitions();
      }
      if (newSelectionElement != this.currentElement) {
        this.changeCurrentElement(newSelectionElement);
      }
    });

    this.modeler.on<any>('root.added', ({element}) => {
      this.changeCurrentElement(element)
    });
  }

  addElementChangedListener(listener: (event: ElementChangeEvent) => void) {
    this.elementChanged.subscribe(listener);
  }

  private changeCurrentElement(element: BpmnElement) {
    this.currentElement = element;
    if (is(element, 'bpmn:Definitions')) {
      this.currentElement = element.rootElements[0];
    } else {
      this.currentElement = element;
    }

    const type = getElementType(this.currentElement);
    this.elementChanged.emit({
      type,
      element: this.currentElement
    })
  }


  loadXml(xml: string) {
    this.modeler.importXML(xml).then(() => {
      console.log('import xml done');
    });
  }

  async createNewDiagram() {
    const moddle = new BpmnModdle();
    const {
      rootElement: definitions
    } = await moddle.fromXML(newDiagramXml as string);

    definitions.set('id', 'DEF-' + uuidv4().toUpperCase());
    definitions.set('name', '新流程');

    const processes: ModdleElement[] = definitions.rootElements;
    let i = 1;
    for (const process of processes) {
      process.set('id', 'PROC-' + uuidv4().toUpperCase());
      process.set('name', '新流程' + i);
      i++;
    }

    const {
      xml: xmlStrUpdated
    } = await moddle.toXML(definitions);

    this.loadXml(xmlStrUpdated);
  }

  generateXml(): Observable<string> {
    const subject: Subject<string> = new Subject();
    this.modeler.saveXML({format: true, preamble: true}).then((res: SaveXMLResult) => {
      subject.next(res.xml || '');
    })
    return subject;
  }


}
